/***************************************************************************
 *   Copyright (C) 2008 by BogDan Vatra                                    *
 *   bogdan@licentia.eu                                                    *
 *                                                                         *
 *   This program is free software: you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation, either version 3 of the License, or     *
 *   (at your option) any later version.                                   *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program.  If not, see <http://www.gnu.org/licenses/>. *
 ***************************************************************************/
#include <QSqlError>
#include <QSqlDriver>

#include "queryselectionmodel.h"
#include "query.h"

#ifdef QT_NO_EXCEPTIONS
#include <QDebug>
#endif

#include <QDebug>
namespace SQL
{

Query::Query(QObject *parent,  const QString & query, QSqlDatabase db)
		: QObject(parent), m_iudQuery(0), m_currentPos(-1), m_queryState(Invalid),m_refreshQuery(false), m_db(db)
{
	m_selectQuery = new QSqlQuery(query, m_db);
	connect(m_db.driver(), SIGNAL(notification(const QString&)), SLOT(notification(const QString&)));
	m_on_event=false;
}


Query::~Query()
{
	delete m_iudQuery;
	delete m_selectQuery;
}

void Query::notification(const QString & name)
{
	if (name==m_listenNotify && m_primaryKey.length())
	{
		if (m_queryState==Select)
		{
			QVariant v=value(m_primaryKey);
			select();
			locate(m_primaryKey,v,true,false,false);
		}
		else
			m_refreshQuery=true;
	}
}

void Query::seek(const QModelIndex& current, const QModelIndex& old)
{
	Q_UNUSED(old);
	if (m_queryState != Select)
		seek(m_currentPos);
	else
		seek(current.row());
}

void Query::setListenNotifyMessage(const QString & message)
{
	if (m_db.driver())
	{
		if (m_listenNotify.length())
			m_db.driver()->unsubscribeFromNotification(m_listenNotify);
		m_db.driver()->subscribeToNotification(message);
	}
	m_listenNotify=message;
}

void Query::setPrivilegesForTable(const QString & table)
{
	m_privilegesForTable=table;
}

bool Query::canSelect()
{
	QSqlQuery qry(m_db);
	if (!qry.exec(QString("SELECT has_table_privilege('%1','select') as priv").arg(m_privilegesForTable)))
		throw(qry.lastError().text());
	qry.first();
	return qry.value(0).toBool();
}

bool Query::canInsert()
{
	QSqlQuery qry(m_db);
	if (!qry.exec(QString("SELECT has_table_privilege('%1','insert') as priv").arg(m_privilegesForTable)))
		throw(qry.lastError().text());
	qry.first();
	return qry.value(0).toBool();
}

bool Query::canUpdate()
{
	QSqlQuery qry(m_db);
	if (!qry.exec(QString("SELECT has_table_privilege('%1','update') as priv").arg(m_privilegesForTable)))
		throw(qry.lastError().text());
	qry.first();
	return qry.value(0).toBool();
}

bool Query::canDelete()
{
	QSqlQuery qry(m_db);
	if (!qry.exec(QString("SELECT has_table_privilege('%1','delete') as priv").arg(m_privilegesForTable)))
		throw(qry.lastError().text());
	qry.first();
	return qry.value(0).toBool();
}

QueryModel * Query::createModel(const QStringList & columns)
{
	return new QueryModel(this, columns);
}

QItemSelectionModel * Query::createSelectionModel(QAbstractItemModel * model)
{
	return new QuerySelectionModel(model, this);
}

const QList<QSqlRecord> & Query::result()
{
	return m_result;
}

void Query::setSelectQuery(const QString & sql)
{
	m_selectSql = sql;
	m_selectQuery->prepare(m_selectSql);
	m_selectPlaceholders=Database::placeHolders(m_selectSql);
}

void Query::setInsertQuery(const QString & sql)
{
	m_insertSql = sql;
}

void Query::setUpdateQuery(const QString & sql)
{
	m_updateSql = sql;
}

void Query::setRemoveQuery(const QString & sql)
{
	m_removeSql = sql;
}


void Query::setRefreshQuery(const QString & sql)
{
	m_refreshSql = sql;
	m_refreshPlaceholders=Database::placeHolders(m_refreshSql);
}

void Query::setPrimaryKey(const QString & primaryKey)
{
	m_primaryKey=primaryKey;
}

int Query::at()
{
	return m_currentPos;
}

bool Query::isActive() const
{
	return m_queryState != Invalid;
}

bool Query::isValid() const
{
	return m_currentPos != -1;
}

Query::QueryState Query::state()
{
	return m_queryState;
}

void Query::setQueryState(QueryState state)
{
	m_queryState=state;
	emit(stateChanged(m_queryState));
}

QSqlRecord Query::record()
{
	if (m_currentPos == -1)
		return QSqlRecord();
	return m_result[m_currentPos];
}

int Query::size()
{
	return m_result.size();
}

bool typeIsNumber(QVariant::Type type)
{
	if (QVariant::Int == type || QVariant::LongLong == type || QVariant::UInt == type || QVariant::ULongLong == type || QVariant::Double == type)
		return true;
	return false;
}

int Query::cmpVariant(const QVariant & val1, const QVariant & val2, bool caseSensitive)
{
	if (typeIsNumber(val1.type()) && typeIsNumber(val2.type()))
		return (val1.value<double>() - val2.value<double>()) < 0 ? -1 : (val1.value<double>() == val2.value<double>()) ? 0 : 1;
	return QString::compare(val1.toString(), val2.toString(), caseSensitive ? Qt::CaseSensitive : Qt::CaseInsensitive);
}

bool Query::locate(const QString & fieldName, const QVariant & value, LocateOptions locateOptions)
{
	return locate(fieldName, value, locateOptions&CaseSensitive, locateOptions&PartialSearch, locateOptions&SortedSearch);
}

bool Query::locate(const QString & fieldName, const QVariant & value, bool caseSensitive, bool partial, bool sortedSearch)
{
	if (!m_result.size() || Select!=m_queryState)
		return false;

	if (m_currentPos<0||m_currentPos>=m_result.size())
		first();

	if (partial)
	{
		if (!cmpVariant(value, m_result[m_currentPos].value(fieldName).toString().left(value.toString().length()), caseSensitive))
			return true;

		if (!cmpVariant(value, m_result[0].value(fieldName).toString().left(value.toString().length()), caseSensitive))
		{
			first();
			return true;
		}

		if (!cmpVariant(value, m_result[m_result.size()-1].value(fieldName).toString().left(value.toString().length()), caseSensitive))
		{
			last();
			return true;
		}
	}
	else
	{
		if (!cmpVariant(m_result[m_currentPos].value(fieldName), value, caseSensitive))
			return true;
		if (!cmpVariant(m_result[0].value(fieldName), value, caseSensitive))
		{
			first();
			return true;
		}
		if (!cmpVariant(m_result[m_result.size()-1].value(fieldName), value, caseSensitive))
		{
			last();
			return true;
		}
	}
	if (!cmpVariant(m_result[m_currentPos].value(fieldName), value, caseSensitive))
		return true;

	if (!sortedSearch)
	{
		if (partial)
		{
			for (int i = 0;i < m_result.size();i++)
			{
				if (!cmpVariant(value.toString(), m_result[i].value(fieldName).toString().left(value.toString().length()), caseSensitive))
				{
					seek(i);
					return true;;
				}
			}
		}
		else
		{
			int i = 0;
			while (1)
			{
				i++;
				if (m_currentPos - i < 0 && m_currentPos + i >= m_result.size())
					break;

				if ((m_currentPos - i >= 0) && (!cmpVariant(value, m_result[m_currentPos-i].value(fieldName), caseSensitive)))
				{
					seek(m_currentPos - i);
					return true;;
				}

				if ((m_currentPos + i < m_result.size()) && (!cmpVariant(value, m_result[m_currentPos+i].value(fieldName), caseSensitive)))
				{
					seek(m_currentPos + i);
					return true;;
				}
			}
		}
	}
	else
	{
		int f = 0, l = m_result.size() - 1;
		while (l > f)
		{
			int p = (f + l) / 2;
			if (!partial)
			{
				if (!cmpVariant(value, m_result[p].value(fieldName), caseSensitive))
				{
					seek(p);
					return true;;
				}

				if (cmpVariant(value, m_result[p].value(fieldName), caseSensitive) > 0)
				{
					if (!cmpVariant(value, m_result[l].value(fieldName), caseSensitive))
					{
						seek(l);
						return true;;
					}
					if (f!=p)
					{
						f = p;
						continue;
					}
					else
						return false;
				}
				if (cmpVariant(value, m_result[p].value(fieldName), caseSensitive) < 0)
				{
					if (!cmpVariant(value, m_result[f].value(fieldName), caseSensitive))
					{
						seek(f);
						return true;;
					}
					if (l!=p)
					{
						l = p;
						continue;
					}
					else
						return false;
				}
			}
			else
			{
				if (!cmpVariant(value.toString(), m_result[p].value(fieldName).toString().left(value.toString().size()), caseSensitive))
				{
					seek(p);
					return true;;
				}

				if (cmpVariant(value.toString(), m_result[p].value(fieldName).toString().left(value.toString().size()), caseSensitive) > 0)
				{
					if (f!=p)
					{
						f = p;
						continue;
					}
					else
						return false;
				}

				if (cmpVariant(value.toString(), m_result[p].value(fieldName).toString().left(value.toString().size()), caseSensitive) < 0)
				{
					if (l!=p)
					{
						l = p;
						continue;
					}
					else
						return false;
				}
			}
		}
	}
	return false;
}

bool Query::locate(int fieldNumber, const QVariant & value, bool caseSensitive, bool partial, bool sortedSearch)
{
	if (!m_result.size())
		return false;
	return locate(m_result[0].fieldName(fieldNumber), value, caseSensitive, partial, sortedSearch);
}

QVariant Query::value(const QString & fieldName)
{
	if (-1 == m_currentPos || m_queryState != Select)
		return QVariant();
	return m_result[m_currentPos].value(fieldName);
}

QVariant Query::value(int fieldNumber)
{
	if (-1 == m_currentPos || m_queryState != Select)
		return QVariant();
	return m_result[m_currentPos].value(fieldNumber);
}

void Query::setValue(QString fieldName, const QVariant & value)
{
	if ((m_queryState != Insert && m_queryState != Update) || !m_iudQuery)
		return;

	if (!fieldName.startsWith(":"))
		fieldName = ":" + fieldName;

	if (m_iudPlaceholders.contains(fieldName))
		m_iudQuery->bindValue(fieldName, value);
}

void Query::setValue(int fieldNumber, const QVariant & value)
{
	if ((m_queryState != Insert && m_queryState != Update) || !m_iudQuery)
		return;

	m_iudQuery->bindValue(fieldNumber, value);
}

void Query::bindValue(QString placeholder, const QVariant & val)
{
	if (!placeholder.startsWith(":"))
		placeholder = ":" + placeholder;
	m_stringBindValues[placeholder]=val;
}

void Query::bindValue(int pos, const QVariant & val)
{
	m_intBindValues[pos]=val;
}

QVariant Query::boundValue(QString placeholder) const
{
	if (!placeholder.startsWith(":"))
		placeholder = ":" + placeholder;
	return m_stringBindValues[placeholder];
}

QVariant Query::boundValue(int pos) const
{
	return m_intBindValues[pos];
}

const QStringList & Query::orderFields()
{
	return m_orderFields;
}

void Query::setOrderFields(const QStringList & orderFields)
{
	m_orderFields = orderFields;
}

int Query::cmpRecords(QSqlRecord rec1, QSqlRecord rec2)
{
	foreach(QString field, m_orderFields)
	{
		int res = cmpVariant(rec1.value(field), rec2.value(field), false);
		if (res)
			return res;
	}
	return 0;
}

void Query::addRecord(const QSqlRecord & record)
{
	if (!m_result.size() || cmpRecords(record, m_result[0]) < 0)
	{
		m_result.push_front(record);
		m_currentPos=0;
		return;
	}
	else
		if (cmpRecords(record, m_result[m_result.size()-1]) > 0)
		{
			m_result.push_back(record);
			m_currentPos=m_result.size()-1;
			return;
		}
	if (cmpRecords(record, m_result[0])<=0)
	{
		m_currentPos=0;
		m_result.push_front(record);
		return;
	}

	if (cmpRecords(record, m_result[m_result.size()-1])>=0)
	{
		m_currentPos=m_result.size();
		m_result.push_back(record);
		return;
	}

	int f = 0, l = m_result.size() - 1;
	while (l > f)
	{
		int p = (f + l) / 2;
		if (cmpRecords(record, m_result[p]) >= 0 && cmpRecords(record, m_result[p+1]) <= 0)
		{
			m_result.insert(p + 1, record);
			m_currentPos=p+1;
			return;
		}

		if (cmpRecords(record, m_result[p]) < 0)
		{
			l = p;
			continue;
		}
		f = p;
		continue;
	}
}

void Query::bindValuesToQuery(QSqlQuery * qry)
{
	if (m_intBindValues.size())
		foreach(int placeholder, m_intBindValues.keys())
			qry->bindValue(placeholder, m_intBindValues[placeholder]);
	else
	{
		QStringList placeholders=Database::placeHolders(qry->lastQuery());
		foreach(QString placeholder, m_stringBindValues.keys())
			if (placeholders.contains(placeholder))
				qry->bindValue(placeholder, m_stringBindValues[placeholder]);
	}
}

void Query::cancel()
{
	delete m_iudQuery;
	m_iudQuery = 0;
	setQueryState(Select);
	seek(m_currentPos);
	emit(afterCancel());
}

void Query::insert()
{
	if (m_queryState == Invalid)
		return;

	if (m_queryState != Select)
		cancel();

	m_iudQuery = new QSqlQuery(m_db);
	m_iudQuery->prepare(m_insertSql);
	m_iudPlaceholders=Database::placeHolders(m_insertSql);
	foreach(QString ph, m_iudPlaceholders)
		m_iudQuery->bindValue(ph,QVariant());
	defaultDatabase()->bindGlobals(m_iudQuery);
	bindValuesToQuery(m_iudQuery);
	setQueryState(Insert);
	emit(beforeInsert());
}

void Query::update()
{
	if (m_queryState == Invalid || m_currentPos == -1)
		return;

	if (m_queryState != Select)
		cancel();

	m_iudQuery = new QSqlQuery(m_db);
	m_iudQuery->prepare(m_updateSql);
	defaultDatabase()->bindGlobals(m_iudQuery);
	bindValuesToQuery(m_iudQuery);
	m_iudPlaceholders=Database::placeHolders(m_updateSql);
	for (int i = 0;i < m_result[m_currentPos].count();i++)
		if (m_iudPlaceholders.contains(":" + m_result[m_currentPos].fieldName(i)))
			m_iudQuery->bindValue(QString(":%1").arg(m_result[m_currentPos].fieldName(i)), m_result[m_currentPos].value(i));

	setQueryState(Update);
	emit(beforeUpdate());
}

void Query::remove()
{
	if (m_queryState == Invalid || m_currentPos == -1)
		return;

	if (m_queryState != Select)
		cancel();

	m_iudQuery = new QSqlQuery(m_db);
	m_iudQuery->prepare(m_removeSql);
	bindValuesToQuery(m_iudQuery);
	m_iudPlaceholders=Database::placeHolders(m_removeSql);
	for (int i = 0;i < record().count();i++)
		if (m_iudPlaceholders.contains(":" + record().fieldName(i)))
			m_iudQuery->bindValue(":" + record().fieldName(i), record().value(i));

	setQueryState(Delete);
	emit(beforeRemove());

	post();
}

void Query::post()
{
	if (!m_iudQuery || m_queryState <= Select)
		return;

	emit(beforePost());

	if (!m_iudQuery->exec())
#ifdef QT_NO_EXCEPTIONS
		qCritical() << m_iudQuery->executedQuery() << m_iudQuery->lastQuery() << m_iudQuery->lastError().text();
#else
		throw QString(tr("Eroare %1\nSQL:%2").arg(m_iudQuery->lastError().text()).arg(m_iudQuery->lastQuery()));
#endif
	else
	{
		switch (m_queryState)
		{
			case Delete:
				m_result.removeAt(m_currentPos);
				if (m_currentPos >= m_result.size())
					m_currentPos--;
				break;

			case Update:
				if (m_iudQuery->isSelect() && m_iudQuery->size() && m_refreshSql.length())
				{
					m_iudQuery->first();
					QSqlQuery qry(m_db);
					qry.prepare(m_refreshSql);
					if (m_iudQuery->size() == 1)
					{
						bindValuesToQuery(&qry);
						QMap<QString, QVariant> bv=m_iudQuery->boundValues();
						foreach(QString placeholder, bv.keys())
							if (m_refreshPlaceholders.contains(placeholder))
								qry.bindValue(placeholder,bv[placeholder]);

						for (int i = 0;i < m_iudQuery->record().count();i++)
							if (m_refreshPlaceholders.contains(":"+m_iudQuery->record().field(i).name()))
								qry.bindValue(":"+m_iudQuery->record().field(i).name(), m_iudQuery->record().field(i).value());

						if (!qry.exec())
#ifdef QT_NO_EXCEPTIONS
							qWarning() << qry.lastError().text();
#else
							throw QString(tr("Eroare %1\nSQL:%2").arg(qry.lastError().text()).arg(qry.lastQuery()));
#endif
						else
						{
							qry.first();
							if (m_orderFields.size())
							{
								m_result.removeAt(m_currentPos);
								addRecord(qry.record());
							}
							else
							{
								m_result[m_currentPos] = qry.record();
							}
						}
					}
				}
				break;

			case Insert:
				if (m_iudQuery->isSelect() && m_iudQuery->size() == 1 && m_refreshSql.length())
				{
					m_iudQuery->first();
					QSqlQuery qry(m_db);
					qry.prepare(m_refreshSql);
					bindValuesToQuery(&qry);
					QMap<QString, QVariant> bv=m_iudQuery->boundValues();
					foreach(QString placeholder, bv.keys())
						if (m_refreshPlaceholders.contains(placeholder))
							qry.bindValue(placeholder,bv[placeholder]);

					for (int i = 0;i < m_iudQuery->record().count();i++)
						if (m_refreshPlaceholders.contains(":" + m_iudQuery->record().field(i).name()))
							qry.bindValue(":" + m_iudQuery->record().field(i).name(), m_iudQuery->record().field(i).value());


					if (!qry.exec())
#ifdef QT_NO_EXCEPTIONS
						qWarning() << qry.lastQuery() << qry.lastError().text();
#else
						throw QString(tr("Eroare %1\nSQL:%2").arg(qry.lastError().text()).arg(qry.lastQuery()));
#endif
					else
					{
						qry.first();
						if (m_orderFields.size())
							addRecord(qry.record());
						else
						{
							m_result.push_back(qry.record());
							m_currentPos=m_result.size()-1;
						}
					}
				}
				break;
			default:
				break;
		}
	}
	delete m_iudQuery;
	m_iudQuery = 0;
	QueryState lastqs=m_queryState;
	setQueryState(Select);
	if (m_refreshQuery)
	{
		QVariant v=value(m_primaryKey);
		select();
		locate(m_primaryKey,v,true,false,false);
		m_refreshQuery=false;
	}
	m_on_event=true;
	switch (lastqs)
	{
		case Delete:
			emit(afterRemove(m_currentPos));
			break;
		case Update:
			emit(afterUpdate(m_currentPos));
			break;
		case Insert:
			emit(afterInsert(m_currentPos));
			break;
	}
	emit(afterPost());
	m_on_event=false;
	if (m_listenNotify.length())
	{
		QSqlQuery qry(m_db);
		qry.exec(QString("NOTIFY %1").arg(m_listenNotify.toLower()));
	}
	seek(m_currentPos);
}

void Query::select()
{
	emit(beforeSelect());
	m_result.clear();
	defaultDatabase()->bindGlobals(m_selectQuery);
	bindValuesToQuery(m_selectQuery);
	if (!m_selectQuery->exec())
#ifdef QT_NO_EXCEPTIONS
	{
		qWarning() << m_selectQuery->lastError().text();
		return;
	}
#else
		throw QString(tr("Eroare %1\nSQL:%2").arg(m_selectQuery->lastError().text()).arg(m_selectQuery->lastQuery()));
#endif
	while (m_selectQuery->next())
		m_result.push_back(m_selectQuery->record());
	m_selectQuery->finish();
	setQueryState(Select);
	first();
	emit(afterSelect());
}

void Query::close(bool clearBoundValues)
{
	m_currentPos=-1;
	m_result.clear();
	setQueryState(Invalid);
	if (clearBoundValues)
	{
		m_stringBindValues.clear();
		m_intBindValues.clear();
	}
	emit(afterClose());
}

bool Query::first()
{
	if (m_queryState != Select || m_on_event)
		return false;

	if (m_result.size())
	{
		m_currentPos = 0;
		emit(cursorFirst(m_result[m_currentPos]));
		seek(m_currentPos);
		return true;
	}
	m_currentPos = -1;
	return false;
}

bool Query::last()
{
	if (m_queryState != Select || m_on_event)
		return false;

	if (m_result.size())
	{
		m_currentPos = m_result.size() - 1;
		emit(cursorLast(m_result[m_currentPos]));
		seek(m_currentPos);
		return true;
	}
	m_currentPos = -1;
	return false;
}

bool Query::previous()
{
	if (m_queryState != Select || m_on_event)
		return false;
	if (!m_result.size())
	{
		m_currentPos = -1;
		return false;
	}

	if (m_currentPos > 0)
	{
		m_currentPos--;
		emit(cursorPrevious(m_result[m_currentPos]));
		seek(m_currentPos);
		return true;
	}
	return false;
}

bool Query::next()
{
	if (m_queryState != Select || m_on_event)
		return false;
	if (!m_result.size())
	{
		m_currentPos = -1;
		return false;
	}

	if (m_result.size() > m_currentPos + 1)
	{
		m_currentPos++;
		emit(cursorNext(m_result[m_currentPos]));
		seek(m_currentPos);
		return true;
	}
	return false;
}

bool Query::seek(int pos)
{
	if (m_queryState != Select || m_on_event)
		return false;
	if (pos < 0 || pos >= m_result.size())
	{
		m_currentPos = -1;
		return false;
	}
	m_currentPos = pos;
	emit(cursorSeek(m_currentPos));
	emit(recordChanged(m_result[m_currentPos]));
	return true;
}

}
